/*
 * NodoHoja.cpp
 *
 *  Created on: 03/04/2010
 *      Author: administrador
 */

#include "NodoHoja.h"

#include "Arbol.h"
#include <iostream>
/*
 * Este constructor se llama cuando el Nodo no existe.
 * Se le debe asignar un id de nodo libre.
 */
NodoHoja::NodoHoja(int idNodo, char tipo) : INodo(idNodo,tipo) {

	this -> nivel = 0;
	this -> cantidadElementos = 0;
	this -> elementos.clear();
	this -> nodoSiguiente = 0;
	this -> nodoAnterior = 0;
	this->tamanioActual += sizeof(this->nodoSiguiente) + sizeof(this->nodoAnterior);

}

NodoHoja::~NodoHoja() {
	for (int i = 0; i < this->cantidadElementos; i++) {

		delete this->elementos[i];

	}
}

Elemento* NodoHoja::getElemento(int posicion) {
	return elementos[posicion];
}

int NodoHoja::getNodoSiguiente() {
	return this -> nodoSiguiente;
}

int NodoHoja::getNodoAnterior() {
	return this -> nodoAnterior;
}

void NodoHoja::setNodoSiguiente(int idNodo) {
	this -> nodoSiguiente = idNodo;
}

void NodoHoja::setNodoAnterior(int idNodo) {
	this -> nodoAnterior = idNodo;
}

void NodoHoja::agregarElemento(Elemento* elemento, int posicion) {

	elementos.resize(cantidadElementos + 1);

	/* Desplazo los elementos en el vector para insertar el nuevo elemento */
	for (int i = cantidadElementos; i > posicion; i--) {

		elementos[i] = elementos[i - 1];
	}

	elementos[posicion] = elemento->clonar();

	cantidadElementos++;

	tamanioActual += this->getTamanioRegistroVariable(elemento);
}

void NodoHoja::borrarElemento(Elemento* elemento, int posicion)
{
	/* Borro el elemento a eliminar */

	if (cantidadElementos>0){/* Se reordena el vector con los elementos que quedan */
		for(int i = posicion + 1 ; i < cantidadElementos; i++)
		{
			elementos[i-1] = elementos[i];
		}
	}
	else{
		delete elementos[posicion];
	}

	cantidadElementos--;

	elementos.resize(cantidadElementos);

	tamanioActual -= getTamanioRegistroVariable(elemento);

	delete elemento;
}

void NodoHoja::agregarElemento(Elemento* elemento) {

	elementos.push_back(elemento);
	cantidadElementos++;
	tamanioActual += this -> getTamanioRegistroVariable(elemento);
}

bool NodoHoja::insertarElemento(Elemento* elemento) {

	/* Busco la posicion donde insertar el nuevo elemento */
	int posicion = 0;

	if (this -> cantidadElementos > 0) {
		while ((posicion < cantidadElementos) && (*elemento > *(elementos[posicion]))) {
			posicion++;
		}
	}

	/* Si ya existe el elemento no lo agrega y devuelve una excepcion */
	if ((posicion < cantidadElementos) && (*elemento == *(elementos[posicion]))) {
		throw ElementoExistenteException(elemento -> getClave());
	}

	/* Se agrega el elemento al vector de elementos en la posicion indicada */
	agregarElemento(elemento, posicion);

	/* Verificacion de OverFlow */
	if (tamanioActual >= factorCargaOverflow) {
		
		throw OverflowException(this -> getId());
	}

	return true;
}

bool NodoHoja::quitarElemento(Clave clave) {
	/* Busco el elemento para eliminar y la posicion en la que está */
	int posicion = 0;

	if (this->cantidadElementos > 0)
	{
		while ((posicion < cantidadElementos) && (clave > elementos[posicion]->getClave()))
		{
			posicion++;
		}
	}

	/* Si el elemento con la clave por parametro no existe devuelve una excepcion */
	if (((posicion < cantidadElementos) && (clave != elementos[posicion]->getClave())) || posicion >= cantidadElementos ) {
		throw ElementoInexistenteException(clave);
	}

	//TODO: Puedo pasar la condicion que sigue? Revisar.
	/* Si el nodo esta vacio lanza la excepcion que lo avise */
	if(cantidadElementos == 0)
	{
		throw NodoVacioException(id);
	}

	/* Como el elemento existe lo quito */
	borrarElemento(elementos[posicion], posicion);

	/* Verificacion de Underflow */
	if (tamanioActual < factorCargaUnderflow)
	{

		throw UnderflowException(this -> id);

	}

	return true;
}

bool NodoHoja::modificarElemento(Clave clave, string* nuevoValor)
{
	Elemento* aModificar;
	try
	{
		aModificar = buscarElemento(clave);
	}
	catch (NodoVacioException ex)
	{
		throw NodoVacioException(ex.getIdNodo());
	}

	/* Si el elemento es distinto de NULL y es el que debo modificar, entonces lo hago */
	if ((aModificar != NULL) && (aModificar -> getClave() == clave))
	{
		/* Le resto al tamaño del nodo el tamaño del valor que se va */
		tamanioActual -= aModificar->getTamanio();

		/* Modifico el valor */
		aModificar -> setValor(nuevoValor);

		/* Le sumo al tamaño del nodo el tamaño del valor nuevo */
		tamanioActual += aModificar -> getTamanio();

		/* Verificacion de OverFlow */
		if (tamanioActual >= factorCargaOverflow)
		{
			throw OverflowException(this -> getId());
		}

		/* Verificacion de UnderFlow */
		if (tamanioActual < factorCargaUnderflow)
		{
			throw UnderflowException(this -> getId());
		}
	}
	else
	{
		throw ElementoInexistenteException(clave);
	}

	return true;
}

Elemento* NodoHoja::buscarElemento(Clave clave) {

	Elemento* aRetornar;

	/* Busco la posicion donde deberia estar el elemento */
	int posicion = 0;

	if (this -> cantidadElementos > 0)
	{
		while ((posicion < cantidadElementos) && (clave > elementos[posicion] -> getClave()))
		{
			posicion++;
		}
	}
	else
	{
		throw NodoVacioException(this -> id);
	}

	if (posicion < this->getCantidadElementos() && elementos[posicion]->getClave()==clave ){

		aRetornar = this -> getElemento(posicion);
//		Arbol::getInstancia() -> setNodoActual(this);
//		Arbol::getInstancia() -> setElementoActual(aRetornar);
	}
	else{

		throw ElementoInexistenteException();
	}

	return aRetornar;
}

Elemento* NodoHoja::primerElemento()
{
	Arbol::getInstancia() -> setNodoActual(this);
	return this -> getElemento(0);
}

Elemento* NodoHoja::getElementoContiguo(int desplazamiento) {

	Elemento* aRetornar;

	Clave claveElementoActual;

	/* Busco la posicion del elemento actual*/
	int posicion = 0;

	if (this -> cantidadElementos > 0)
	{
		claveElementoActual = Arbol::getInstancia() -> getElementoActual() -> getClave();
		while ((posicion < cantidadElementos) && (claveElementoActual > elementos[posicion] -> getClave()))
		{
			posicion++;
		}
	}
	else
	{
		throw NodoVacioException(this -> id);
	}

	NodoHoja* nodoContiguo = NULL;

	int elementoNodoContiguo = 0;

	int idNodoContiguo = 0;

	if ((posicion == 0 && desplazamiento==NodoHoja::EIZQUIERDO) || ( posicion ==this->cantidadElementos -1 && desplazamiento == NodoHoja::EDERECHO )){

		if (desplazamiento ==NodoHoja::EDERECHO){

			if ( (idNodoContiguo = this->getNodoSiguiente())!=0){

				nodoContiguo = new NodoHoja(this->getNodoSiguiente());

				nodoContiguo->hidratar();

				elementoNodoContiguo = 0;

			}
			else{

				throw ElementoUltimoException();

			}

		}

		if (desplazamiento==NodoHoja::EIZQUIERDO){

			if ((idNodoContiguo = this->getNodoAnterior())!=0){

				nodoContiguo = new NodoHoja(this->getNodoAnterior());

				nodoContiguo->hidratar();

				elementoNodoContiguo = nodoContiguo->cantidadElementos-1;

			}
			else{

				throw ElementoPrimeroException();
			}

		}

		try
		{
			aRetornar = nodoContiguo->getElemento(elementoNodoContiguo)->clonar();

			Arbol::getInstancia()->setNodoActual(nodoContiguo);

		}
		catch (NodoVacioException ex)
		{
			throw NodoVacioException(ex.getIdNodo());
		}
		catch (ElementoInexistenteException ex)
		{
			throw ElementoInexistenteException();
		}

	}else
	{
		if (desplazamiento==NodoHoja::EDERECHO){

			desplazamiento = 1;
		}
		else{

			desplazamiento = -1;
		}

		aRetornar = this -> getElemento(posicion + desplazamiento)->clonar();

	}

	Arbol::getInstancia() -> setElementoActual(aRetornar);

	if (nodoContiguo){

		delete nodoContiguo;
	}

	return aRetornar;
}

void NodoHoja::serializar() {

	if (this->cantidadElementos>0){

	c_bloque *buffer = new c_bloque();

	buffer->set_nro(this->id);

	buffer->add(this->id);

	buffer->add(this->cantidadElementos);

	buffer->add(this->tamanioActual);

	buffer->add(this->nivel);

	buffer->add(this->nodoAnterior);

	buffer->add(this->nodoSiguiente);

	for (int i = 0; i < this->cantidadElementos; i++) {

		this->elementos[i]->serializar(buffer);

	}

	buffer->save();

	delete buffer;
	}
	else{
		Archivo::getInstance()->borrarBloque(this->id);
	}
}

Clave NodoHoja::verificarSeparador(){

	return this->elementos[0]->getClave();

}

void NodoHoja::hidratar() {

	if (this->id < 0) {

		this->id = Archivo::getInstance()->getBloqueLibre();

	} else {
		c_bloque *bufferNodo = new c_bloque();

		bufferNodo->set_nro(this->id);

		bufferNodo->load();

		bufferNodo->get(&(this->id));

		bufferNodo->get(&(this->cantidadElementos));

		bufferNodo->get(&(this->tamanioActual));

		bufferNodo->get(&(this->nivel));

		bufferNodo->get(&(this->nodoAnterior));

		bufferNodo->get(&(this->nodoSiguiente));

		this->elementos.resize(this->cantidadElementos);

		for (int i = 0; i < this->cantidadElementos; i++) {

			this->elementos[i] = new Elemento();

			this->elementos[i]->hidratar(bufferNodo);

		}

		delete bufferNodo;

	}
}

int NodoHoja::getTamanioRegistroVariable(Elemento *elemento) {

	return (elemento->getTamanio());
}

void NodoHoja::printNodo(ofstream* archivoSalida,int cantidadNiveles) {

	string tab;

	for (int k = 0; k < cantidadNiveles - this->nivel; k++) {

		tab += '\t';
	}

	*archivoSalida << tab << "Numero Nodo " << this->id << ", " << this->cantidadElementos << ", " << this->nivel << " Tamaño Actual " << this->tamanioActual;


	*archivoSalida << "  NodoAnterior " << this->nodoAnterior << "   NodoSiguiente " << this->nodoSiguiente;

	*archivoSalida << std::endl;
	for (int i = 0; i < this->cantidadElementos; i++) {

		*archivoSalida << tab << this->elementos[i]->getClave().getClave();
		*archivoSalida << " (Largo elemento: " << getTamanioRegistroVariable(this->elementos[i]) <<") ";
	}

	*archivoSalida << std::endl;

}

void NodoHoja::actualizarNodosContiguos(INodo* nodoNuevo) {

	int idSiguiente = this->getNodoSiguiente();

	if (idSiguiente!=0)
	{
		NodoHoja* nodoSiguiente = new NodoHoja(idSiguiente);

		nodoSiguiente -> hidratar();

		nodoSiguiente -> setNodoAnterior(nodoNuevo -> getId());

		nodoSiguiente -> serializar();

		delete nodoSiguiente;

	}
	else
	{
		((NodoHoja*)nodoNuevo)->setNodoSiguiente(idSiguiente);
	}

	((NodoHoja*)nodoNuevo) -> setNodoAnterior(this -> getId());

	((NodoHoja*)nodoNuevo) -> setNodoSiguiente(idSiguiente);

	this -> setNodoSiguiente(nodoNuevo -> getId());
}

void NodoHoja::partirNodoEnOverflow(Clave &claveAPromover, INodo* nodoNuevo) {

	int tamanioMedio = this -> tamanioActual/2;
	int i = this -> cantidadElementos - 1;

		Elemento* elementoActual;

		elementoActual = this -> getElemento(i);

	while ( this -> tamanioActual - getTamanioRegistroVariable(elementoActual) > tamanioMedio){


		((NodoHoja*) nodoNuevo) -> agregarElemento(elementoActual, 0);

		/* Si el elemento actual del Arbol se pasa al nodo nuevo, entoces aquel sera el nodo actual */

		this -> cantidadElementos--;

		this -> setTamanioActual(this-> getTamanioActual() - this -> getTamanioRegistroVariable(elementoActual));

		i--;

		delete elementoActual;

		elementoActual = this -> getElemento(i);
	}

	claveAPromover = ((NodoHoja*)nodoNuevo) -> getElemento(0) -> getClave();

	this -> elementos.resize(this -> cantidadElementos);

	/* Actualizacion de dependencias del set secuencial */
	actualizarNodosContiguos(nodoNuevo);
}

bool NodoHoja::balancearNodoEnUnderflow(Clave clavePadre,INodo* nodoHermano)
{
	Clave clave;
	/* Si el tamaño de ambos supera dos veces el tamaño de Underflow
	 * entonces se supone que se pueden balancear sin producir Under en ninguno */
	if((tamanioActual + nodoHermano -> getTamanioActual()) > (2 * factorCargaUnderflow) && nodoHermano->getCantidadElementos()>0 && this->getCantidadElementos()>0)
	{

		/* Me fijo si se fusiona con el hermano Derecho o el Izquierdo, los fusiono y luego los parto */

		if(this->fusionarNodoEnUnderflow(clavePadre,nodoHermano))
		{
			this->partirNodoEnOverflow(clave,nodoHermano);
			return true;
		}

	}

	return false;
}

bool NodoHoja::fusionarNodoEnUnderflow(Clave clavePadre, INodo* nodoHermano)
{
	/* Si la suma de los dos tamaños supera el tamañoMaximo de nodo entonces
	 * no se fusionan porque se perderian datos */
	if((tamanioActual + nodoHermano->getTamanioActual())>=(Arbol::tamanioMaximoNodo))
	{
		return false;
	}

		/* Se fusiona con el hermano Derecho, se agregan los elementos del hermano al final del this */
		for(int i = 0; i < nodoHermano->getCantidadElementos(); i++)
		{
			this->agregarElemento(((NodoHoja*)nodoHermano)->getElemento(i));
		}

		if (((NodoHoja*)nodoHermano)->getNodoSiguiente()!=0){

			NodoHoja *nuevoNodoSiguiente = new NodoHoja(((NodoHoja*)nodoHermano)->getNodoSiguiente());

			nuevoNodoSiguiente->hidratar();

			this->nodoSiguiente = nuevoNodoSiguiente->getId();

			nuevoNodoSiguiente->nodoAnterior = this->getId();

			nuevoNodoSiguiente->serializar();

			delete nuevoNodoSiguiente;
		}
		else{

			this->nodoSiguiente=0;
		}

		nodoHermano->vaciarNodo();

		return true;
}

void NodoHoja::vaciarNodo(){

	this->elementos.resize(0);
	this->cantidadElementos = 0;
	this->tamanioActual = this->getTamanioElementosControlNodo() + sizeof(this->nodoAnterior) + sizeof(this->nodoSiguiente);
}

NodoHoja* NodoHoja::clonar()
{
	NodoHoja* clon = new NodoHoja(this -> id);

	clon -> tipo = this -> tipo;
	clon -> nivel = this -> nivel;
	clon -> nodoAnterior = this -> nodoAnterior;
	clon -> nodoSiguiente = this -> nodoSiguiente;
	clon->cantidadElementos = this->cantidadElementos;

	for (int i = 0; i < this -> cantidadElementos; i++)
	{

		clon -> agregarElemento(this -> getElemento(i), i);
	}

	return clon;
}

